package core

import (
	"bytes"
	"context"
	"dcim/pkg/generic-go-kit/core/coder"
	"dcim/pkg/generic-go-kit/utils"
	"encoding/json"
	"errors"
	"github.com/go-kit/kit/endpoint"
	natstransport "github.com/go-kit/kit/transport/nats"
	"github.com/nats-io/nats.go"
	"github.com/nats-io/nats.go/micro"
	"io/ioutil"
	"net/http"
	"time"
)

type NatsTransportI interface {
	MakeDecodeRequestFunc() natstransport.DecodeRequestFunc
	MakeEncodeRequestFunc() natstransport.EncodeRequestFunc
	MakeDecodeResponseFunc() natstransport.DecodeResponseFunc
	MakeEncodeResponseFunc() natstransport.EncodeResponseFunc
}

type NatsTransport[REQ, RESP any] struct {
	EndpointName string
}

func (t *NatsTransport[REQ, RESP]) MakeEncodeRequestFunc() natstransport.EncodeRequestFunc {
	return natstransport.EncodeJSONRequest
}

func (t *NatsTransport[REQ, RESP]) MakeDecodeResponseFunc() natstransport.DecodeResponseFunc {
	return func(ctx context.Context, msg *nats.Msg) (response interface{}, err error) {
		var resp RESP
		var reader = bytes.NewReader(msg.Data)

		err = json.NewDecoder(reader).Decode(&resp)
		if err != nil {
			return resp, errors.New("decode failed ")
		}
		return resp, nil
	}
}

func (t *NatsTransport[REQ, RESP]) MakeEncodeResponseFunc() natstransport.EncodeResponseFunc {
	return natstransport.EncodeJSONResponse
}

func (t *NatsTransport[REQ, RESP]) MakeDecodeRequestFunc() natstransport.DecodeRequestFunc {
	return func(ctx context.Context, msg *nats.Msg) (interface{}, error) {
		var req REQ
		var reader = bytes.NewReader(msg.Data)

		err := json.NewDecoder(reader).Decode(&req)
		if err != nil {
			return req, errors.New("decode failed ")
		}
		return req, nil
	}
}

func DecodeNatsResponseFunc(ctx context.Context, msg *nats.Msg) (response interface{}, err error) {
	// 引用自定义的order map以保证转成json字符串时字段顺序不会变化
	var r utils.OrderedMap
	//var r map[string]interface{}

	err = json.Unmarshal(msg.Data, &r)
	if err != nil {
		return nil, err
	}
	return r, nil
}

// Publisher wraps a URL and provides a method that implements endpoint.Endpoint.
type Publisher struct {
	publisher *nats.Conn
	subject   string
	enc       natstransport.EncodeRequestFunc
	dec       natstransport.DecodeResponseFunc
	before    []natstransport.RequestFunc
	after     []natstransport.PublisherResponseFunc
	timeout   time.Duration
}

// NewNatsPublisher constructs a usable Publisher for a single remote method.
func NewNatsPublisher(
	publisher *nats.Conn,
	subject string,
	enc natstransport.EncodeRequestFunc,
	dec natstransport.DecodeResponseFunc,
	options ...PublisherOption,
) *Publisher {
	p := &Publisher{
		publisher: publisher,
		subject:   subject,
		enc:       enc,
		dec:       dec,
		timeout:   10 * time.Second,
	}
	for _, option := range options {
		option(p)
	}
	return p
}

// PublisherOption sets an optional parameter for clients.
type PublisherOption func(*Publisher)

// PublisherBefore sets the RequestFuncs that are applied to the outgoing NATS
// request before it's invoked.
func PublisherBefore(before ...natstransport.RequestFunc) PublisherOption {
	return func(p *Publisher) { p.before = append(p.before, before...) }
}

// PublisherAfter sets the ClientResponseFuncs applied to the incoming NATS
// request prior to it being decoded. This is useful for obtaining anything off
// of the response and adding onto the context prior to decoding.
func PublisherAfter(after ...natstransport.PublisherResponseFunc) PublisherOption {
	return func(p *Publisher) { p.after = append(p.after, after...) }
}

// PublisherTimeout sets the available timeout for NATS request.
func PublisherTimeout(timeout time.Duration) PublisherOption {
	return func(p *Publisher) { p.timeout = timeout }
}

// Endpoint returns a usable endpoint that invokes the remote endpoint.
func (p Publisher) Endpoint() endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		ctx, cancel := context.WithTimeout(ctx, p.timeout)
		defer cancel()

		msg := nats.Msg{Subject: p.subject}

		if err := p.enc(ctx, &msg, request); err != nil {
			return nil, err
		}

		for _, f := range p.before {
			ctx = f(ctx, &msg)
		}

		resp, err := p.publisher.RequestMsgWithContext(ctx, &msg)
		if err != nil {
			return nil, err
		}

		for _, f := range p.after {
			ctx = f(ctx, resp)
		}

		response, err := p.dec(ctx, resp)
		if err != nil {
			return nil, err
		}

		return response, nil
	}
}

type NatsMicroTransportI interface {
	MakeDecodeRequestFunc() coder.DecodeNatsMicroRequestFunc
	MakeEncodeRequestFunc() coder.EncodeNatsMicroRequestFunc
	MakeDecodeResponseFunc() coder.DecodeNatsMicroResponseFunc
	MakeEncodeResponseFunc() coder.EncodeNatsMicroResponseFunc
}

type NatsMicroTransport[REQ, RESP any] struct {
	EndpointName string
}

func (t *NatsMicroTransport[REQ, RESP]) MakeEncodeRequestFunc() coder.EncodeNatsMicroRequestFunc {
	return coder.EncodeJSONToNatsMicroRequest
}

func (t *NatsMicroTransport[REQ, RESP]) MakeDecodeResponseFunc() coder.DecodeNatsMicroResponseFunc {
	return func(ctx context.Context, req micro.Request) (response interface{}, err error) {
		var resp RESP
		var reader = bytes.NewReader(req.Data())

		err = json.NewDecoder(reader).Decode(&resp)
		if err != nil {
			return resp, errors.New("decode failed ")
		}
		return resp, nil
	}
}

func (t *NatsMicroTransport[REQ, RESP]) MakeEncodeResponseFunc() coder.EncodeNatsMicroResponseFunc {
	return natstransport.EncodeJSONResponse
}

func (t *NatsMicroTransport[REQ, RESP]) MakeDecodeRequestFunc() coder.DecodeNatsMicroRequestFunc {
	return func(ctx context.Context, req micro.Request) (interface{}, error) {
		var request REQ
		var reader = bytes.NewReader(req.Data())

		err := json.NewDecoder(reader).Decode(&request)
		if err != nil {
			return request, errors.New("decode failed ")
		}
		return request, nil
	}
}

func DecodeResponseFunc(ctx context.Context, req micro.Request) (response interface{}, err error) {
	// 引用自定义的order map以保证转成json字符串时字段顺序不会变化
	var r utils.OrderedMap
	//var r map[string]interface{}

	err = json.Unmarshal(req.Data(), &r)
	if err != nil {
		return nil, err
	}
	return r, nil
}

type HttpTransportI interface {
	MakeDecodeRequestFunc() coder.DecodeHttpRequestFunc
	MakeEncodeRequestFunc() coder.EncodeHttpRequestFunc
	MakeDecodeResponseFunc() coder.DecodeHttpResponseFunc
	MakeEncodeResponseFunc() coder.EncodeHttpResponseFunc
}

type HttpTransport[REQ, RESP any] struct {
	EndpointName string
}

func (t *HttpTransport[REQ, RESP]) MakeEncodeRequestFunc() coder.EncodeHttpRequestFunc {
	return func(_ context.Context, r *http.Request, request interface{}) error {
		var buf bytes.Buffer
		if err := json.NewEncoder(&buf).Encode(request); err != nil {
			return err
		}
		r.Body = ioutil.NopCloser(&buf)
		return nil
	}
}

func (t *HttpTransport[REQ, RESP]) MakeDecodeResponseFunc() coder.DecodeHttpResponseFunc {
	return func(_ context.Context, r *http.Response) (interface{}, error) {
		if r.StatusCode != http.StatusOK {
			return nil, errors.New(r.Status)
		}
		var resp RESP
		err := json.NewDecoder(r.Body).Decode(&resp)
		return resp, err
	}
}

func (t *HttpTransport[REQ, RESP]) MakeEncodeResponseFunc() coder.EncodeHttpResponseFunc {
	return func(ctx context.Context, w http.ResponseWriter, response interface{}) error {
		if f, ok := response.(endpoint.Failer); ok && f.Failed() != nil {
			return f.Failed()
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		return json.NewEncoder(w).Encode(response)
	}
}

func (t *HttpTransport[REQ, RESP]) MakeDecodeRequestFunc() coder.DecodeHttpRequestFunc {
	return func(_ context.Context, r *http.Request) (interface{}, error) {
		var request REQ
		//_ = r.ParseForm()
		err := json.NewDecoder(r.Body).Decode(&request)
		return request, err
	}
}
